#include "XobjIO.h"

#include "..\rtCommon\xobj_string.h"

#pragma warning(disable: 4996)
#pragma warning(disable: 4267)

SMaterial * Material(SMaterial * pMaterial)
{
  if (pMaterial == NULL)
      return NULL;

  set(pMaterial->ambient);
  set(pMaterial->diffuse);
  set(pMaterial->specular);
  set(pMaterial->emission);

  pMaterial->shininess = 0.0f;

  return pMaterial;
}

CXobjIO::CXobjIO(void)
{
	m_bIsLight = false;
	Matrix4x4SetIdentity( matrixWS );
  Material( &material );
}

CXobjIO::~CXobjIO(void)
{
	Release();
}

CXobjIO::CXobjIO(const CXobjIO &xobj)
{
	Release();

	m_bIsLight = xobj.m_bIsLight;

	uv = xobj.uv;
	pos = xobj.pos;
	nml = xobj.nml;
	clr = xobj.clr;
	posIdx = xobj.posIdx;
	uvIdx = xobj.uvIdx;
	clrIdx = xobj.clrIdx;

	matrixWS = xobj.matrixWS;
}

void CXobjIO::Release(void)
{
	uv.clear();
	pos.clear();
	nml.clear();
	clr.clear();
	posIdx.clear();
	uvIdx.clear();
	clrIdx.clear();

	m_bIsLight = false;

    Material( &material );

	Matrix4x4SetIdentity( &matrixWS );
}

int CXobjIO::Load(std::vector<CXobjIO *> &vXobjList, const char * fname)
{
	CXobjIO * pXobj = NULL;

	char aString[XOBJ_MAX_STRING_LENGTH];

	int iv = 0, ivn = 0, ivc = 0, ivt = 0, ifv = 0, ift = 0, ifc = 0;
	int numV = 0, numF = 0, numTV = 0, numCV = 0;    

  if (fname == NULL ) return XOBJ_INVALID_ARGS;

  FILE *pFile = fopen(fname, "r");
  if (pFile == NULL) return XOBJ_FILE_NOT_FOUND;

  while (!feof(pFile))
  {
		memset(aString, 0, XOBJ_MAX_STRING_LENGTH);
		fscanf(pFile, "%s", aString);

    // -------------------------------------------------------------------- Node Counter
    if (strcmp(aString, STRING_NODE_COUNTER) == 0)
    {     
      iv = ivn = ivc = ivt = ifv = ift = ifc = 0;
			numV = numF = numTV = numCV = 0;

      pXobj = new CXobjIO();

      if (pXobj != NULL) vXobjList.push_back(pXobj);

      pXobj->m_bIsLight = false;

      continue;
    }
		// -------------------------------------------------------------------- Array sizes
		if (strcmp(aString, STRING_FACE_COUNT) == 0)
		{			
			fscanf(pFile, "%d", &numF);

			numF *= 3;
			pXobj->posIdx.resize(numF);

			continue;
		}

		if (strcmp(aString, STRING_VERTEX_COUNT) == 0)
		{
			fscanf(pFile, "%d", &numV);
			
			pXobj->pos.resize(numV);
			pXobj->nml.resize(numV);

			continue;
		}

		if (strcmp(aString, STRING_TEXTURE_COUNT) == 0)
		{
			fscanf(pFile, "%d", &numTV);		

			pXobj->uv.resize(numTV);
			pXobj->uvIdx.resize(numF);

			continue;
		}

		if (strcmp(aString, STRING_COLOR_COUNT) == 0)
		{
			fscanf(pFile, "%d", &numCV);

			pXobj->clr.resize(numCV);
			pXobj->clrIdx.resize(numF);
			
			continue;
		}

		// -------------------------------------------------------------------- Materials
		if (strcmp(aString, STRING_V_AMBIENT) == 0)
		{
      float4 c = { 0, 0, 0, 1 };
			fscanf(pFile, "%f%f%f", &c.x, &c.y, &c.z);
			pXobj->material.ambient = c;
			continue;
		}

		if (strcmp(aString, STRING_V_DIFFUSE) == 0)
		{
      float4 c = { 0, 0, 0, 1 };
			fscanf(pFile, "%f%f%f", &c.x, &c.y, &c.z);
			pXobj->material.diffuse = c;
			continue;
		}

		if (strcmp(aString, STRING_V_SPECULAR) == 0)
		{
      float4 c = { 0, 0, 0, 1 };
			fscanf(pFile, "%f%f%f", &c.x, &c.y, &c.z);
			pXobj->material.specular = c;
			continue;
		}

		if (strcmp(aString, STRING_V_EMISSION) == 0)
		{
      float4 c = { 0, 0, 0, 1 };
			fscanf(pFile, "%f%f%f", &c.x, &c.y, &c.z);
			pXobj->material.emission = c;
			if (length(c) > 0) pXobj->m_bIsLight = true;
			continue;
		}

		if (strcmp(aString, STRING_V_SHININESS) == 0)
		{
			float s = 0.0f;
			fscanf(pFile, "%f", &s);
			pXobj->material.shininess = s;
			continue;
		}
		
		// -------------------------------------------------------------------- Bounding Box
		if (strcmp(aString, STRING_BBOX_PMAX) == 0)
		{
			float3 m_Pmax;
			fscanf(pFile, "%f%f%f", &m_Pmax.x, &m_Pmax.y, &m_Pmax.z);
			pXobj->m_AABB.pmax = m_Pmax;
			continue;
		}
		if (strcmp(aString, STRING_BBOX_PMIN) == 0)
		{
			float3 m_Pmin;
			fscanf(pFile, "%f%f%f", &m_Pmin.x, &m_Pmin.y, &m_Pmin.z);
			pXobj->m_AABB.pmin = m_Pmin;
			continue;
		}

		// -------------------------------------------------------------------- Model Transform Matrix
		if (strcmp(aString, STRING_V_MODEL_MATRIX) == 0)
		{
			float r[] = {0.0f, 0.0f, 0.0f};

			Matrix4x4SetIdentity( &(pXobj->matrixWS) );

			for (int irow = 0; irow < 4; irow++)
			{
				fscanf(pFile, "%f%f%f", &r[0], &r[1], &r[2]);

				pXobj->matrixWS.row[irow].x = r[0];
				pXobj->matrixWS.row[irow].y = r[1];
				pXobj->matrixWS.row[irow].z = r[2];
			}
			continue;
		}

		// -------------------------------------------------------------------- Vertex Attributes
		if (strcmp(aString, STRING_V_MODEL ) == 0)
		{
#ifdef _DEBUG
			if (pXobj->pos.capacity() == 0) return XOBJ_INVALID_FILE_FORMAT;
#endif
			float4 v = {0.0f, 0.0f, 0.0f, 1.0f};
			fscanf(pFile, "%f%f%f", &v.x, &v.y, &v.z);
			pXobj->pos[iv++] = v;
			continue;
		}

		if (strcmp(aString, STRING_VN) == 0)
		{
#ifdef _DEBUG
			if (pXobj->nml.capacity() == 0) return XOBJ_INVALID_FILE_FORMAT;
#endif
			float4 n = {0.0f, 0.0f, 0.0f, 1.0f};
			fscanf(pFile, "%f%f%f", &n.x, &n.y, &n.z);
			pXobj->nml[ivn++] = n;
			continue;
		}

		if (strcmp(aString, STRING_VC) == 0)
		{
#ifdef _DEBUG
			if (pXobj->clr.capacity() == 0) return XOBJ_INVALID_FILE_FORMAT;
#endif
			float4 c = {0.0f, 0.0f, 0.0f, 1.0f};
			fscanf(pFile, "%f%f%f", &c.x, &c.y, &c.z);
			pXobj->clr[ivc++] = c;
			continue;
		}

		if (strcmp(aString, STRING_VT) == 0)
		{
#ifdef _DEBUG
			if (pXobj->uv.capacity() == 0) return XOBJ_INVALID_FILE_FORMAT;
#endif
			float3 uv = {0.0f, 0.0f, 0.0f};
			fscanf(pFile, "%f%f%f", &uv.x, &uv.y, &uv.z);
			pXobj->uv[ivt++] = xy(uv);
			continue;
		}

		// -------------------------------------------------------------------- Faces
		if (strcmp(aString, STRING_FV_XOBJ) == 0)
		{
#ifdef _DEBUG
			if (pXobj->pos.capacity() == 0 || pXobj->nml.capacity() == 0) return XOBJ_INVALID_FILE_FORMAT;
#endif
			int3 vf = {0, 0, 0};
			fscanf(pFile, "%d%d%d", &vf.x, &vf.y, &vf.z);
			
			pXobj->posIdx[ifv++] = vf.x;
      pXobj->posIdx[ifv++] = vf.y;
      pXobj->posIdx[ifv++] = vf.z;

			continue;
		}

		if (strcmp(aString, STRING_FC_XOBJ) == 0)
		{
#ifdef _DEBUG
			if (pXobj->clrIdx.capacity() == 0) return XOBJ_INVALID_FILE_FORMAT;
#endif
			int4 vfc = {0, 0, 0, -1};
			fscanf(pFile, "%d%d%d", &vfc.x, &vfc.y, &vfc.z);
			pXobj->clrIdx[ifc++] = vfc;
			continue;
		}

		if (strcmp(aString, STRING_FT_XOBJ) == 0)
		{
#ifdef _DEBUG
			if (pXobj->uvIdx.capacity() == 0) return XOBJ_INVALID_FILE_FORMAT;
#endif
			int4  vft = {0, 0, 0, -1};
			fscanf(pFile, "%d%d%d", &vft.x, &vft.y, &vft.z);
			pXobj->uvIdx[ift++] = vft;
			continue;
		}
  }

  for (uint32 iXobj = 0; iXobj < vXobjList.size(); iXobj++)
  {
    FaceNormal ( vXobjList[iXobj] );
    //Tangent    ( vXobjList[iXobj] );
  }

	return XOBJ_OK;
}

void CXobjIO::TransformWS()
{
  uint32 vsize = pos.size();
  
  float4 f4posWS = pos[0] * matrixWS;
  float3 f3posWS = xyz(f4posWS);
  m_AABB.pmin = m_AABB.pmax = f3posWS;
  
  //position[0] = f4posWS;

  for (uint32 ivert = 0; ivert < vsize; ivert++)
  {
    f4posWS = pos[ivert] * matrixWS;
    f3posWS = xyz(f4posWS);

    m_AABB.pmin = min(m_AABB.pmin, f3posWS);
    m_AABB.pmax = max(m_AABB.pmax, f3posWS); 

    pos[ivert] = f4posWS;

    float3 f3Normal = xyz(nml[ivert]);

    float3 f3normalWS = Matrix4x4TransformDirectionR(f3Normal, matrixWS);

    set(nml[ivert], normalize(f3normalWS), 0);
  }

  Matrix4x4SetIdentity( &matrixWS );
}

void FaceNormal(CXobjIO * pXobj)
{
  if (pXobj == NULL)
      return;

  for (uint32 iFace = 0; iFace < pXobj->posIdx.size(); iFace+=3)
  {
    int3 idx = { pXobj->posIdx[iFace + 0], 
                 pXobj->posIdx[iFace + 1], 
                 pXobj->posIdx[iFace + 2] };

    float3 v0 = xyz(pXobj->pos[idx.x]);
    float3 v1 = xyz(pXobj->pos[idx.y]);
    float3 v2 = xyz(pXobj->pos[idx.z]);

    float3 e1 = v0 - v1;
    float3 e2 = v0 - v2;

    float4 f4FaceNormal = f4(normalize(cross(e1, e2)), 0);

    pXobj->m_FaceNormal.push_back(f4FaceNormal);
  }
}

void TangentBasis(CXobjIO * pXobj)
{
  if (pXobj == NULL) return;
	
	// need UVs to compute bi / tangents
  if (pXobj->uv.size() == 0) return;

  uint32 vertexCount = pXobj->pos.size();

  float3 * pTan1 = new float3[vertexCount];
  float3 * pTan2 = new float3[vertexCount];

  pXobj->tnt.clear();
  pXobj->bnt.clear();

  pXobj->tnt.resize(vertexCount);
  pXobj->bnt.resize(vertexCount);

  for (uint32 iFace = 0; iFace < pXobj->posIdx.size(); iFace+=3)
  {
    int3 idx = { pXobj->posIdx[iFace + 0], 
                 pXobj->posIdx[iFace + 1], 
                 pXobj->posIdx[iFace + 2]};
    
    float4 v1 = pXobj->pos[idx.x];
    float4 v2 = pXobj->pos[idx.y];
    float4 v3 = pXobj->pos[idx.x];

    int4 uvidx = pXobj->uvIdx[iFace / 3];

    float2 w1 = pXobj->uv[uvidx.x];
    float2 w2 = pXobj->uv[uvidx.y];
    float2 w3 = pXobj->uv[uvidx.z];

    float x1 = v2.x - v1.x;
    float x2 = v3.x - v1.x;
    float y1 = v2.y - v1.y;
    float y2 = v3.y - v1.y;
    float z1 = v2.z - v1.z;
    float z2 = v3.z - v1.z;

    float s1 = w2.x - w1.x;
    float s2 = w3.x - w1.x;
    float t1 = w2.y - w1.y;
    float t2 = w3.y - w1.y;

    float r = 1.0f / (s1 * t2 - s2 * t1);
    
    float3 sdir = { (t2 * x1 - t1 * x2) * r, 
                    (t2 * y1 - t1 * y2) * r,
                    (t2 * z1 - t1 * z2) * r };

    float3 tdir = { (s1 * x2 - s2 * x1) * r, 
                    (s1 * y2 - s2 * y1) * r,
                    (s1 * z2 - s2 * z1) * r };

    pTan1[idx.x] += sdir;
    pTan1[idx.y] += sdir;
    pTan1[idx.z] += sdir;

    pTan2[idx.x] += tdir;
    pTan2[idx.y] += tdir;
    pTan2[idx.z] += tdir;
  }

  for (uint32 iAttr = 0; iAttr < vertexCount; iAttr++)
  {
    float3 n = normalize3( pXobj->nml[iAttr] );
    float3 t = pTan1[iAttr];

    // Gram-Schmidt orthogonalize
    t = normalize(t - n * dot(n, t));

    // Calculate handedness
    float s = (dot(cross(n, t), pTan2[iAttr]) < 0.0f) ? -1.0f : 1.0f;

    float3 b = normalize(cross(n, t)) * s;

		pXobj->tnt[iAttr] = f4(b, 0);
		pXobj->tnt[iAttr] = f4(t, s);
		pXobj->tnt[iAttr] = f4(n, 0);
  }

  delete [] pTan1;
  delete [] pTan2;
}